﻿#include "stdafx.h"
#include <logging.h>
#include <RimeWithWeasel.h>
#include <StringAlgorithm.hpp>
#include <WeaselUtility.h>
#include <WeaselVersion.h>
#include <regex>
#include <rime_api.h>
#include <map>

#define TRANSPARENT_COLOR	0x00000000
#define ARGB2ABGR(value)	((value & 0xff000000) | ((value & 0x000000ff) << 16) | (value & 0x0000ff00) | ((value & 0x00ff0000) >> 16)) 
#define RGBA2ABGR(value)    (((value & 0xff) << 24) | ((value & 0xff000000) >> 24) | ((value & 0x00ff0000) >> 8) | ((value & 0x0000ff00) << 8))
typedef enum
{
	COLOR_ABGR = 0,
	COLOR_ARGB,
	COLOR_RGBA
} ColorFormat;

#ifdef USE_SHARP_COLOR_CODE
	#define HEX_REGEX		std::regex("^(0x|#)[0-9a-f]+$", std::regex::icase)
	#define TRIMHEAD_REGEX	std::regex("0x|#", std::regex::icase)
#else
	#define HEX_REGEX		std::regex("^0x[0-9a-f]+$", std::regex::icase)
	#define TRIMHEAD_REGEX	std::regex("0x", std::regex::icase)
#endif
using namespace weasel;

int expand_ibus_modifier(int m)
{
	return (m & 0xff) | ((m & 0xff00) << 16);
}

#define SHOWN_ASCII (1<<0)
#define SHOWN_SHAPE (1<<1)
#define SHOWN_ASCII_PUNCT (1<<2)
#define SHOWN_SIMPLIFICATION (1<<3)
#define SHOWN_SCHEMA (1<<4)
#define SHOWN_ALWAYS    (SHOWN_ASCII|SHOWN_SHAPE|SHOWN_ASCII_PUNCT|SHOWN_SCHEMA|SHOWN_SIMPLIFICATION)
#define SHOWN_NEVER  0x00

RimeWithWeaselHandler::RimeWithWeaselHandler(UI *ui)
	: m_ui(ui)
	, m_active_session(0)
	, m_disabled(true)
	, m_current_dark_mode(false)
	, m_show_notifications_when(SHOWN_ALWAYS)
	, _UpdateUICallback(NULL)
{
	_Setup();
}

RimeWithWeaselHandler::~RimeWithWeaselHandler()
{
}

void _UpdateShowNotificationsWhen(RimeConfig* config, UINT* show_notifications_when);
void _UpdateUIStyle(RimeConfig* config, UI* ui, bool initialize);
bool _UpdateUIStyleColor(RimeConfig* config, UIStyle& style, std::string color = "");
void _LoadAppOptions(RimeConfig* config, AppOptionsByAppName& app_options);

void _RefreshTrayIcon(const UINT session_id, const std::function<void()> _UpdateUICallback)
{
	// Dangerous, don't touch
	static char app_name[50];
	RimeGetProperty(session_id, "client_app", app_name, sizeof(app_name) - 1);
	if (string_to_wstring(app_name, CP_UTF8) == std::wstring(L"explorer.exe")) 
		boost::thread th([=]() { ::Sleep(100); if (_UpdateUICallback) _UpdateUICallback(); });
	else 
		if (_UpdateUICallback) _UpdateUICallback();
}

void RimeWithWeaselHandler::_Setup()
{
	RIME_STRUCT(RimeTraits, weasel_traits);
	weasel_traits.shared_data_dir = weasel_shared_data_dir();
	weasel_traits.user_data_dir = weasel_user_data_dir();
	weasel_traits.prebuilt_data_dir = weasel_traits.shared_data_dir;
	std::string distribution_name(wstring_to_string(WEASEL_IME_NAME, CP_UTF8));
	weasel_traits.distribution_name = distribution_name.c_str();
	weasel_traits.distribution_code_name = WEASEL_CODE_NAME;
	weasel_traits.distribution_version = WEASEL_VERSION;
	weasel_traits.app_name = "rime.weasel";
	RimeSetup(&weasel_traits);
	RimeSetNotificationHandler(&RimeWithWeaselHandler::OnNotify, this);
}

void RimeWithWeaselHandler::Initialize()
{
	m_disabled = _IsDeployerRunning();
	if (m_disabled)
	{
		 return;
	}

	LOG(INFO) << "Initializing la rime.";
	RimeInitialize(NULL);
	if (RimeStartMaintenance(/*full_check = */False))
	{
		m_disabled = true;
	}

	RimeConfig config = { NULL };
	if (RimeConfigOpen("weasel", &config))
	{
		if (m_ui)
		{
			_UpdateUIStyle(&config, m_ui, true);
			_UpdateShowNotificationsWhen(&config, &m_show_notifications_when);
			m_current_dark_mode = IsUserDarkMode();
			if(m_current_dark_mode) {
				const int BUF_SIZE = 255;
				char buffer[BUF_SIZE + 1] = { 0 };
				if (RimeConfigGetString(&config, "style/color_scheme_dark", buffer, BUF_SIZE)){
					std::string color_name(buffer);
					_UpdateUIStyleColor(&config, m_ui->style(), color_name);
				}
			}
			m_base_style = m_ui->style();
		}
		_LoadAppOptions(&config, m_app_options);
		RimeConfigClose(&config);
	}
	m_last_schema_id.clear();
}

void RimeWithWeaselHandler::Finalize()
{
	m_active_session = 0;
	m_disabled = true;
	LOG(INFO) << "Finalizing la rime.";
	RimeFinalize();
}

UINT RimeWithWeaselHandler::FindSession(UINT session_id)
{
	if (m_disabled) return 0;
	Bool found = RimeFindSession(session_id);
	DLOG(INFO) << "Find session: session_id = " << session_id << ", found = " << found;
	return found ? session_id : 0;
}

UINT RimeWithWeaselHandler::AddSession(LPWSTR buffer, EatLine eat)
{
	if (m_disabled)
	{
		DLOG(INFO) << "Trying to resume service.";
		EndMaintenance();
		if (m_disabled) return 0;
	}
	UINT session_id = RimeCreateSession();
	DLOG(INFO) << "Add session: created session_id = " << session_id;
	_ReadClientInfo(session_id, buffer);

	RIME_STRUCT(RimeStatus, status);
	if (RimeGetStatus(session_id, &status))
	{
		std::string schema_id = status.schema_id;
		m_last_schema_id = schema_id;
		_LoadSchemaSpecificSettings(schema_id);
		_LoadAppInlinePreeditSet(session_id, true);
		_UpdateInlinePreeditStatus(session_id);
		_RefreshTrayIcon(session_id, _UpdateUICallback);
		RimeFreeStatus(&status);
	}
	// show session's welcome message :-) if any
	if (eat) {
		_Respond(session_id, eat);
	}
	_UpdateUI(session_id);
	m_active_session = session_id;
	m_color_sync[session_id] = false;
	return session_id;
}

UINT RimeWithWeaselHandler::RemoveSession(UINT session_id)
{
	if (m_ui) m_ui->Hide();
	if (m_disabled) return 0;
	DLOG(INFO) << "Remove session: session_id = " << session_id;
	// TODO: force committing? otherwise current composition would be lost
	RimeDestroySession(session_id);
	m_color_sync.erase(session_id);
	m_active_session = 0;
	return 0;
}

namespace ibus
{
	enum Keycode
	{
		Escape = 0xFF1B,
		XK_bracketleft = 0x005b,  /* U+005B LEFT SQUARE BRACKET */
		XK_c = 0x0063,            /* U+0063 LATIN SMALL LETTER C */
		XK_C = 0x0043,            /* U+0043 LATIN CAPITAL LETTER C */
	};
}

void RimeWithWeaselHandler::UpdateColorTheme(BOOL darkMode)
{
	RimeConfig config = { NULL };
	if (RimeConfigOpen("weasel", &config))
	{
		if (m_ui)
		{
			_UpdateUIStyle(&config, m_ui, true);
			m_current_dark_mode = darkMode;
			if(darkMode) {
				const int BUF_SIZE = 255;
				char buffer[BUF_SIZE + 1] = { 0 };
				if (RimeConfigGetString(&config, "style/color_scheme_dark", buffer, BUF_SIZE)) {
					std::string color_name(buffer);
					_UpdateUIStyleColor(&config, m_ui->style(), color_name);
				}
			}
			m_base_style = m_ui->style();
		}
		_LoadAppOptions(&config, m_app_options);
		RIME_STRUCT(RimeStatus, status);
		if (RimeGetStatus(m_active_session, &status))
		{
			_LoadSchemaSpecificSettings(std::string(status.schema_id));
			_LoadAppInlinePreeditSet(m_active_session, true);
			_UpdateInlinePreeditStatus(m_active_session);
			RimeFreeStatus(&status);
		}
		RimeConfigClose(&config);
	}

	RimeSetOption(m_active_session, "__synced", false);
	_LoadAppInlinePreeditSet(m_active_session);
	_UpdateInlinePreeditStatus(m_active_session);
	for(auto &pair : m_color_sync)
		pair.second = false;
}

BOOL RimeWithWeaselHandler::ProcessKeyEvent(KeyEvent keyEvent, UINT session_id, EatLine eat)
{
	DLOG(INFO) << "Process key event: keycode = " << keyEvent.keycode << ", mask = " << keyEvent.mask
		 << ", session_id = " << session_id;
	if (m_disabled) return FALSE;
	Bool handled = RimeProcessKey(session_id, keyEvent.keycode, expand_ibus_modifier(keyEvent.mask));
	if(!handled) {
		bool isVimBackInCommandMode = (keyEvent.keycode == ibus::Keycode::Escape) || 
			((keyEvent.mask & (1 << 2)) && (keyEvent.keycode == ibus::Keycode::XK_c ||
				keyEvent.keycode == ibus::Keycode::XK_c || 
				keyEvent.keycode == ibus::Keycode::XK_bracketleft));
		if (isVimBackInCommandMode 
				&& RimeGetOption(session_id, "vim_mode")
				&& !RimeGetOption(session_id, "ascii_mode")) {
			RimeSetOption(session_id, "ascii_mode", True);
		}
	}
	_Respond(session_id, eat);
	_UpdateUI(session_id);
	m_active_session = session_id;
	return (BOOL)handled;
}

void RimeWithWeaselHandler::CommitComposition(UINT session_id)
{
	DLOG(INFO) << "Commit composition: session_id = " << session_id;
	if (m_disabled) return;
	RimeCommitComposition(session_id);
	_UpdateUI(session_id);
	m_active_session = session_id;
}

void RimeWithWeaselHandler::ClearComposition(UINT session_id)
{
	DLOG(INFO) << "Clear composition: session_id = " << session_id;
	if (m_disabled) return;
	RimeClearComposition(session_id);
	_UpdateUI(session_id);
	m_active_session = session_id;
}

void RimeWithWeaselHandler::SelectCandidateOnCurrentPage(size_t index, UINT session_id)
{
	DLOG(INFO) << "select candidate on current page, session_id = " << session_id << ", index = " << index;
	if (m_disabled) return;
	RimeApi* api = rime_get_api();
	api->select_candidate_on_current_page(session_id, index);
}

void RimeWithWeaselHandler::FocusIn(DWORD client_caps, UINT session_id)
{
	DLOG(INFO) << "Focus in: session_id = " << session_id << ", client_caps = " << client_caps;
	if (m_disabled) return;
	_UpdateUI(session_id);
	m_active_session = session_id;
}

void RimeWithWeaselHandler::FocusOut(DWORD param, UINT session_id)
{
	DLOG(INFO) << "Focus out: session_id = " << session_id;
	if (m_ui) m_ui->Hide();
	m_active_session = 0;
}

void RimeWithWeaselHandler::UpdateInputPosition(RECT const& rc, UINT session_id)
{
	DLOG(INFO) << "Update input position: (" << rc.left << ", " << rc.top
		<< "), session_id = " << session_id << ", m_active_session = " << m_active_session;
	if (m_ui) m_ui->UpdateInputPosition(rc);
	if (m_disabled) return;
	if (m_active_session != session_id)
	{
		_UpdateUI(session_id);
		m_active_session = session_id;
	}
}

std::string RimeWithWeaselHandler::m_message_type;
std::string RimeWithWeaselHandler::m_message_value;

void RimeWithWeaselHandler::OnNotify(void* context_object,
	                                 uintptr_t session_id,
                                     const char* message_type,
                                     const char* message_value)
{
	// may be running in a thread when deploying rime
	RimeWithWeaselHandler* self = reinterpret_cast<RimeWithWeaselHandler*>(context_object);
	if (!self || !message_type || !message_value) return;
	m_message_type = message_type;
	m_message_value = message_value;
}

void RimeWithWeaselHandler::_ReadClientInfo(UINT session_id, LPWSTR buffer)
{
	std::string app_name;
	std::string client_type;
	// parse request text
	wbufferstream bs(buffer, WEASEL_IPC_BUFFER_LENGTH);
	std::wstring line;
	while (bs.good())
	{
		std::getline(bs, line);
		if (!bs.good())
			break;
		// file ends
		if (line == L".")
			break;
		const std::wstring kClientAppKey = L"session.client_app=";
		if (starts_with(line, kClientAppKey))
		{
			std::wstring lwr = line;
			to_lower(lwr);
			app_name = wstring_to_string(lwr.substr(kClientAppKey.length()).c_str(), CP_UTF8);
		}
		const std::wstring kClientTypeKey = L"session.client_type=";
		if (starts_with(line, kClientTypeKey))
		{
			client_type = wstring_to_string(line.substr(kClientTypeKey.length()).c_str(), CP_UTF8);
		}
	}
    // set app specific options
	if (!app_name.empty())
	{
		RimeSetProperty(session_id, "client_app", app_name.c_str());

		auto it = m_app_options.find(app_name);
		if (it != m_app_options.end())
		{
			AppOptions& options(m_app_options[it->first]);
			std::for_each(options.begin(), options.end(), [session_id](std::pair<const std::string, bool> &pair)
			{
				DLOG(INFO) << "set app option: " << pair.first << " = " << pair.second;
				RimeSetOption(session_id, pair.first.c_str(), Bool(pair.second));
			});
		}
	}
	// ime | tsf
	RimeSetProperty(session_id, "client_type", client_type.c_str());
	// inline preedit
	bool inline_preedit = m_ui->style().inline_preedit && (client_type == "tsf");	
	RimeSetOption(session_id, "inline_preedit", Bool(inline_preedit));
	// show soft cursor on weasel panel but not inline
	RimeSetOption(session_id, "soft_cursor", Bool(!inline_preedit));
}

void RimeWithWeaselHandler::_GetCandidateInfo(CandidateInfo & cinfo, RimeContext & ctx)
{
	cinfo.candies.resize(ctx.menu.num_candidates);
	cinfo.comments.resize(ctx.menu.num_candidates);
	cinfo.labels.resize(ctx.menu.num_candidates);
	for (int i = 0; i < ctx.menu.num_candidates; ++i)
	{
		cinfo.candies[i].str = std::regex_replace(string_to_wstring(ctx.menu.candidates[i].text, CP_UTF8),  std::wregex(L"\\r\\n|\\n|\\r"), L"\r");
		if (ctx.menu.candidates[i].comment)
		{
			cinfo.comments[i].str = std::regex_replace(string_to_wstring(ctx.menu.candidates[i].comment, CP_UTF8),  std::wregex(L"\\r\\n|\\n|\\r"), L"\r");
		}
		if (RIME_STRUCT_HAS_MEMBER(ctx, ctx.select_labels) && ctx.select_labels)
		{
			cinfo.labels[i].str = std::regex_replace(string_to_wstring(ctx.select_labels[i], CP_UTF8),  std::wregex(L"\\r\\n|\\n|\\r"), L"\r");
		}
		else if (ctx.menu.select_keys)
		{
			cinfo.labels[i].str = std::regex_replace(std::wstring(1, ctx.menu.select_keys[i]), std::wregex(L"\\r\\n|\\n|\\r"), L"\r");
		}
		else
		{
			cinfo.labels[i].str = std::to_wstring((i + 1) % 10);
		}
	}
	cinfo.highlighted = ctx.menu.highlighted_candidate_index;
	cinfo.currentPage = ctx.menu.page_no;
	cinfo.is_last_page = ctx.menu.is_last_page;
}

void RimeWithWeaselHandler::StartMaintenance()
{
	Finalize();
	_UpdateUI(0);
}

void RimeWithWeaselHandler::EndMaintenance()
{
	if (m_disabled)
	{
		Initialize();
		_UpdateUI(0);
	}
}

void RimeWithWeaselHandler::SetOption(UINT session_id, const std::string & opt, bool val)
{
	RimeSetOption(session_id, opt.c_str(), val);
}

void RimeWithWeaselHandler::OnUpdateUI(std::function<void()> const &cb)
{
	_UpdateUICallback = cb;
}

bool RimeWithWeaselHandler::_IsDeployerRunning()
{
	HANDLE hMutex = CreateMutex(NULL, TRUE, L"WeaselDeployerMutex");
	bool deployer_detected = hMutex && GetLastError() == ERROR_ALREADY_EXISTS;
	if (hMutex)
	{
		CloseHandle(hMutex);
	}
	return deployer_detected;
}

void RimeWithWeaselHandler::_UpdateUI(UINT session_id)
{
	Status weasel_status;
	Context weasel_context;

	bool is_tsf = _IsSessionTSF(session_id);

	if (session_id == 0)
		weasel_status.disabled = m_disabled;

	_GetStatus(weasel_status, session_id, weasel_context);

	if (!is_tsf) {
		_GetContext(weasel_context, session_id);
	}

	if (!m_ui) return;

	if (RimeGetOption(session_id, "inline_preedit"))
		m_ui->style().client_caps |= INLINE_PREEDIT_CAPABLE;
	else
		m_ui->style().client_caps &= ~INLINE_PREEDIT_CAPABLE;

	if (weasel_status.composing)
	{
		m_ui->Update(weasel_context, weasel_status);
		if (!is_tsf) m_ui->Show();
	}
	else if (!_ShowMessage(weasel_context, weasel_status))
	{
		m_ui->Hide();
		m_ui->Update(weasel_context, weasel_status);
	}
	
	_RefreshTrayIcon(session_id, _UpdateUICallback);

	m_message_type.clear();
	m_message_value.clear();
}

void _LoadIconSettingFromSchema(RimeConfig& config, char *buffer, const int& BUF_SIZE, 
		const char* key1, const char* key2, const std::wstring& user_dir, const std::wstring& shared_dir, std::wstring& value)
{
	memset(buffer, '\0', (BUF_SIZE+1));
	if (RimeConfigGetString(&config, key1, buffer, BUF_SIZE) || (key2 != NULL && RimeConfigGetString(&config, key2, buffer, BUF_SIZE))) {
		std::wstring tmp = string_to_wstring(buffer, CP_UTF8);
		DWORD dwAttrib = GetFileAttributes((user_dir + L"\\" + tmp).c_str());
		if (!(INVALID_FILE_ATTRIBUTES != dwAttrib && 0 == (dwAttrib & FILE_ATTRIBUTE_DIRECTORY))) {
			dwAttrib = GetFileAttributes((shared_dir + L"\\" + tmp).c_str());
			if (!(INVALID_FILE_ATTRIBUTES != dwAttrib && 0 == (dwAttrib & FILE_ATTRIBUTE_DIRECTORY)))
				value = L"";
			else
				value = (shared_dir + L"\\" + tmp);
		}
		else value = user_dir + L"\\" + tmp;
	}
	else value = L"";
}

void RimeWithWeaselHandler::_LoadSchemaSpecificSettings(const std::string& schema_id)
{
	if (!m_ui) return;
	const int BUF_SIZE = 255;
	char buffer[BUF_SIZE + 1];
	RimeConfig config;
	if (!RimeSchemaOpen(schema_id.c_str(), &config)) return;
	m_ui->style() = m_base_style;
	_UpdateUIStyle(&config, m_ui, false);

	// load schema color style config
	memset(buffer, '\0', sizeof(buffer));
	if (!m_current_dark_mode && RimeConfigGetString(&config, "style/color_scheme", buffer, BUF_SIZE))
	{
		std::string color_name(buffer);
		RimeConfigIterator preset = {0};
		if(RimeConfigBeginMap(&preset, &config, ("preset_color_schemes/" + color_name).c_str()))
		{
			_UpdateUIStyleColor(&config, m_ui->style(), color_name);
		}
		else
		{
			RimeConfig weaselconfig;
			if (RimeConfigOpen("weasel", &weaselconfig))
			{
				_UpdateUIStyleColor(&weaselconfig, m_ui->style(), std::string(buffer));
				RimeConfigClose(&weaselconfig);
			}
		}
	}
	else if (m_current_dark_mode && RimeConfigGetString(&config, "style/color_scheme_dark", buffer, BUF_SIZE))
	{
		std::string color_name(buffer);
		RimeConfigIterator preset = {0};
		if(RimeConfigBeginMap(&preset, &config, ("preset_color_schemes/" + color_name).c_str()))
		{
			_UpdateUIStyleColor(&config, m_ui->style(), color_name);
		}
		else
		{
			RimeConfig weaselconfig;
			if (RimeConfigOpen("weasel", &weaselconfig))
			{
				_UpdateUIStyleColor(&weaselconfig, m_ui->style(), std::string(buffer));
				RimeConfigClose(&weaselconfig);
			}
		}
	}
	// load schema icon start
	{
		std::wstring user_dir = string_to_wstring(weasel_user_data_dir());
		std::wstring shared_dir = string_to_wstring(weasel_shared_data_dir());
		_LoadIconSettingFromSchema(config, buffer, BUF_SIZE, "schema/icon", "schema/zhung_icon", user_dir, shared_dir, m_ui->style().current_zhung_icon);
		_LoadIconSettingFromSchema(config, buffer, BUF_SIZE, "schema/ascii_icon", NULL, user_dir, shared_dir, m_ui->style().current_ascii_icon);
		_LoadIconSettingFromSchema(config, buffer, BUF_SIZE, "schema/full_icon", NULL, user_dir, shared_dir, m_ui->style().current_full_icon);
		_LoadIconSettingFromSchema(config, buffer, BUF_SIZE, "schema/half_icon", NULL, user_dir, shared_dir, m_ui->style().current_half_icon);
	}
	// load schema icon end
	RimeConfigClose(&config);
}

void RimeWithWeaselHandler::_LoadAppInlinePreeditSet(UINT session_id, bool ignore_app_name)
{
	static char _app_name[50];
	RimeGetProperty(session_id, "client_app", _app_name, sizeof(_app_name) - 1);
	std::string app_name(_app_name);
	if(!ignore_app_name && m_last_app_name == app_name)
		return;
	m_last_app_name = app_name;
	bool inline_preedit = m_ui->style().inline_preedit;
	if (!app_name.empty())
	{
		auto it = m_app_options.find(app_name);
		if (it != m_app_options.end())
		{
			AppOptions& options(m_app_options[it->first]);
			auto pfind = std::make_shared<bool>(false);
			std::for_each(options.begin(), options.end(), [session_id, pfind, inline_preedit, this](std::pair<const std::string, bool> &pair)
			{
			if(pair.first == "inline_preedit")
			{
				*pfind = true;
				RimeSetOption(session_id, pair.first.c_str(), Bool(pair.second));
				m_ui->style().inline_preedit = Bool(pair.second);
				if(m_ui->style().inline_preedit != inline_preedit)
					_UpdateInlinePreeditStatus(session_id);
			}
			});
			if (!(*pfind))
			{
				goto load_schema_inline;
			}
		}
		else
		{
load_schema_inline:
			m_ui->style().inline_preedit = m_base_style.inline_preedit;
			RIME_STRUCT(RimeStatus, status);
			if (RimeGetStatus(session_id, &status))
			{
				std::string schema_id = status.schema_id;
				RimeConfig config;
				if (!RimeSchemaOpen(schema_id.c_str(), &config)) return;
				Bool inline_preedit = m_ui->style().inline_preedit;
				if (RimeConfigGetBool(&config, "style/inline_preedit", &inline_preedit))
					m_ui->style().inline_preedit = !!inline_preedit;
				RimeConfigClose(&config);
				RimeFreeStatus(&status);
				if(m_ui->style().inline_preedit != inline_preedit)
					_UpdateInlinePreeditStatus(session_id);
			}
		}
	}
}

bool RimeWithWeaselHandler::_ShowMessage(Context& ctx, Status& status) {
	// show as auxiliary string
	std::wstring& tips(ctx.aux.str);
	bool show_icon = false;
	if (m_message_type == "deploy") {
		if (m_message_value == "start")
			tips = L"正在部署 RIME";
		else if (m_message_value == "success")
			tips = L"部署完成";
		else if (m_message_value == "failure")
		{
			if( GetThreadUILanguage() == MAKELANGID(LANG_CHINESE, SUBLANG_CHINESE_TRADITIONAL) )
				tips = L"有錯誤，請查看日誌 %TEMP%\\rime.weasel.*.INFO";
			else
				tips = L"有错误，请查看日志 %TEMP%\\rime.weasel.*.INFO";
		}
	}
	else if (m_message_type == "schema") {
		tips = /*L"【" + */status.schema_name/* + L"】"*/;
	}
	else if (m_message_type == "option") {
		if (m_message_value == "!ascii_mode")
		{
			show_icon = true;  
		}
		else if (m_message_value == "ascii_mode")
		{
			show_icon = true;  
		}
		else if (m_message_value == "!full_shape")
			tips = L"半角";
		else if (m_message_value == "full_shape")
			tips = L"全角";
		else if (m_message_value == "!ascii_punct")
			tips = L"，。";
		else if (m_message_value == "ascii_punct")
			tips = L"，．";
		else if (m_message_value == "!simplification")
			tips = L"漢字";
		else if (m_message_value == "simplification")
			tips = L"汉字";
	}
	if (tips.empty() && !show_icon)
		return m_ui->IsCountingDown();
	if( ((m_show_notifications_when & SHOWN_ASCII) && (m_message_value == "ascii_mode" || m_message_value == "!ascii_mode"))
			|| ((m_show_notifications_when & SHOWN_SHAPE) && (m_message_value == "full_shape" || m_message_value == "!full_shape"))
			|| ((m_show_notifications_when & SHOWN_ASCII_PUNCT) && (m_message_value == "ascii_punct" || m_message_value == "!ascii_punct"))
			|| ((m_show_notifications_when & SHOWN_SIMPLIFICATION) && (m_message_value == "simplification" || m_message_value == "!simplification"))
			|| (m_message_type == "schema" && (m_show_notifications_when && SHOWN_SCHEMA))
			|| m_message_type == "deploy") { m_ui->Update(ctx, status);
		m_ui->ShowWithTimeout(1200 + 200 * tips.length());
		return true;
	} else {
		return m_ui->IsCountingDown();
	}
}
inline std::string _GetLabelText(const std::vector<Text> &labels, int id, const wchar_t *format)
{
	wchar_t buffer[128];
	swprintf_s<128>(buffer, format, labels.at(id).str.c_str());
	return wstring_to_string(std::wstring(buffer), CP_UTF8);
}

bool RimeWithWeaselHandler::_Respond(UINT session_id, EatLine eat)
{
	std::set<std::string> actions;
	std::list<std::string> messages;

	// load App inline_preedit setting if app_name changed
	_LoadAppInlinePreeditSet(session_id);
	// extract information

	RIME_STRUCT(RimeCommit, commit);
	if (RimeGetCommit(session_id, &commit))
	{
		actions.insert("commit");
		messages.push_back(std::string("commit=") + commit.text + '\n');
		RimeFreeCommit(&commit);
	}
	
	bool is_composing = false;
	RIME_STRUCT(RimeStatus, status);
	if (RimeGetStatus(session_id, &status))
	{
		is_composing = !!status.is_composing;
		actions.insert("status");
		messages.push_back(std::string("status.ascii_mode=") + std::to_string(status.is_ascii_mode) + '\n');
		messages.push_back(std::string("status.composing=") + std::to_string(status.is_composing) + '\n');
		messages.push_back(std::string("status.disabled=") + std::to_string(status.is_disabled) + '\n');
		messages.push_back(std::string("status.full_shape=") + std::to_string(status.is_full_shape) + '\n');
		messages.push_back(std::string("status.schema_id=") + std::string(status.schema_id) + '\n');
		RimeFreeStatus(&status);
	}
	
	RIME_STRUCT(RimeContext, ctx);
	if (RimeGetContext(session_id, &ctx))
	{
		if (is_composing)
		{
			actions.insert("ctx");
			switch (m_ui->style().preedit_type)
			{
			case UIStyle::PREVIEW:
				if (ctx.commit_text_preview != NULL)
				{
					std::string first = ctx.commit_text_preview;
					messages.push_back(std::string("ctx.preedit=") + first + '\n');
					messages.push_back(std::string("ctx.preedit.cursor=") +
						std::to_string(utf8towcslen(first.c_str(), 0)) + ',' +
						std::to_string(utf8towcslen(first.c_str(), first.size())) + ',' +
						std::to_string(utf8towcslen(first.c_str(), first.size())) + '\n');
					break;
				}
				// no preview, fall back to composition
			case UIStyle::COMPOSITION:
				messages.push_back(std::string("ctx.preedit=") + ctx.composition.preedit + '\n');
				if (ctx.composition.sel_start <= ctx.composition.sel_end)
				{
					messages.push_back(std::string("ctx.preedit.cursor=") +
						std::to_string(utf8towcslen(ctx.composition.preedit, ctx.composition.sel_start)) + ',' +
						std::to_string(utf8towcslen(ctx.composition.preedit, ctx.composition.sel_end)) + ',' +
						std::to_string(utf8towcslen(ctx.composition.preedit, ctx.composition.cursor_pos)) + '\n');
				}
				break;
			case UIStyle::PREVIEW_ALL:
				CandidateInfo cinfo;
				_GetCandidateInfo(cinfo, ctx);
				std::string topush = std::string("ctx.preedit=") + ctx.composition.preedit + "  [";
				for (auto i = 0; i < ctx.menu.num_candidates; i++)
				{
					std::string label = m_ui->style().label_font_point > 0 ? _GetLabelText(cinfo.labels, i, m_ui->style().label_text_format.c_str()) : "";
					std::string comment = m_ui->style().comment_font_point > 0 ? wstring_to_string(cinfo.comments.at(i).str, CP_UTF8) : "";
					std::string mark_text = m_ui->style().mark_text.empty() ? "*" : wstring_to_string(m_ui->style().mark_text, CP_UTF8);
					std::string prefix = (i != ctx.menu.highlighted_candidate_index) ? "" : mark_text;
					topush += " " + prefix + label + std::string(ctx.menu.candidates[i].text) + " " + comment;
				}
				messages.push_back(topush + " ]\n");
				//messages.push_back(std::string("ctx.preedit=") + ctx.composition.preedit + '\n');
				if (ctx.composition.sel_start <= ctx.composition.sel_end)
				{
					messages.push_back(std::string("ctx.preedit.cursor=") +
						std::to_string(utf8towcslen(ctx.composition.preedit, ctx.composition.sel_start)) + ',' +
						std::to_string(utf8towcslen(ctx.composition.preedit, ctx.composition.sel_end)) + ',' +
						std::to_string(utf8towcslen(ctx.composition.preedit, ctx.composition.cursor_pos)) + '\n');
				}
				break;
			}
		}
		if (ctx.menu.num_candidates)
		{
			CandidateInfo cinfo;
			std::wstringstream ss;
			boost::archive::text_woarchive oa(ss);
			_GetCandidateInfo(cinfo, ctx);

			oa << cinfo;

			messages.push_back(std::string("ctx.cand=") + wstring_to_string(ss.str().c_str(), CP_UTF8) + '\n');
		}
		RimeFreeContext(&ctx);
	}

	// configuration information
	actions.insert("config");
	messages.push_back(std::string("config.inline_preedit=") + std::to_string((int)m_ui->style().inline_preedit) + '\n');

	// style
	bool has_synced = RimeGetOption(session_id, "__synced");
	if (!has_synced || !m_color_sync[session_id]) {
		std::wstringstream ss;
		boost::archive::text_woarchive oa(ss);
		oa << m_ui->style();

		actions.insert("style");
		messages.push_back(std::string("style=") + wstring_to_string(ss.str().c_str(), CP_UTF8) + '\n');
		if(!has_synced)
			RimeSetOption(session_id, "__synced", true);
		m_color_sync[session_id] = TRUE;
	}

	// summarize

	if (actions.empty())
	{
		messages.insert(messages.begin(), std::string("action=noop\n"));
	}
	else
	{
		std::string actionList(join(actions, ","));
		messages.insert(messages.begin(), std::string("action=") + actionList + '\n');
	}

	messages.push_back(std::string(".\n"));

	return std::all_of(messages.begin(), messages.end(), [&eat](std::string &msg)
	{
		return eat(std::wstring(string_to_wstring(msg.c_str(), CP_UTF8)));
	});
}

static inline COLORREF blend_colors(COLORREF fcolor, COLORREF bcolor)
{
	return RGB(
		(GetRValue(fcolor) * 2 + GetRValue(bcolor)) / 3,
		(GetGValue(fcolor) * 2 + GetGValue(bcolor)) / 3,
		(GetBValue(fcolor) * 2 + GetBValue(bcolor)) / 3
		) | ((((fcolor >> 24)+(bcolor >> 24)/2) << 24));
}
// convertions from color format to COLOR_ABGR
static inline int ConvertColorToAbgr(int color, ColorFormat fmt = COLOR_ABGR)
{
	if(fmt == COLOR_ABGR) return color;
	else if(fmt == COLOR_ARGB) return ARGB2ABGR(color);
	else return RGBA2ABGR(color);
}
// parse color value, with fallback value
static Bool _RimeConfigGetColor32bWithFallback(RimeConfig* config, const std::string key, int& value, const ColorFormat& fmt, const int& fallback)
{
	char color[256] = { 0 };
	if (!RimeConfigGetString(config, key.c_str(), color, 256))
	{
		value = fallback;
		return False;
	}
	std::string color_str = std::string(color);
	// color code hex 
	if (std::regex_match(color_str, HEX_REGEX))
	{
		std::string tmp = std::regex_replace(color_str, TRIMHEAD_REGEX, "");
		// limit first 8 code
		tmp = tmp.substr(0, 8);
		if(tmp.length() == 6) // color code without alpha, xxyyzz add alpha ff
		{
			value = std::stoi(tmp, 0, 16);
			if(fmt != COLOR_RGBA) value |= 0xff000000;
			else value = (value << 8) | 0x000000ff;
		}
		else if(tmp.length() == 3) // color hex code xyz => xxyyzz and alpha ff
		{
			tmp = tmp.substr(0, 1) + tmp.substr(0, 1)
				+ tmp.substr(1, 1) + tmp.substr(1, 1)
				+ tmp.substr(2, 1) + tmp.substr(2, 1);
			
			value = std::stoi(tmp, 0, 16);
			if(fmt != COLOR_RGBA) value |= 0xff000000;
			else value = (value << 8) | 0x000000ff;
		}
		else if(tmp.length() == 4)	// color hex code vxyz => vvxxyyzz
		{
			tmp = tmp.substr(0, 1) + tmp.substr(0, 1)
				+ tmp.substr(1, 1) + tmp.substr(1, 1)
				+ tmp.substr(2, 1) + tmp.substr(2, 1)
				+ tmp.substr(3, 1) + tmp.substr(3, 1);
			
			std::string tmp1 = tmp.substr(0, 6);
			int value1 = std::stoi(tmp1, 0, 16);
			tmp1 = tmp.substr(6);
			int value2 = std::stoi(tmp1, 0, 16);
			value = (value1 << (tmp1.length() * 4)) | value2;
		}
		else if(tmp.length() > 6 && tmp.length() <= 8)	/* color code with alpha */
		{
			// stoi limitation, split to handle
			std::string tmp1 = tmp.substr(0, 6);
			int value1 = std::stoi(tmp1, 0, 16);
			tmp1 = tmp.substr(6);
			int value2 = std::stoi(tmp1, 0, 16);
			value = (value1 << (tmp1.length() * 4)) | value2;
		}
		else	// reject other code, length less then 3 or length == 5
		{
			value = fallback;
			return False;
		}
		value = ConvertColorToAbgr(value, fmt);
		value = (value & 0xffffffff);
		return True;
	}
	// regular number or other stuff, if user use pure dec number, they should take care themselves
	else
	{
		int tmp = 0;
		if (!RimeConfigGetInt(config, key.c_str(), &tmp))
		{
			value = fallback;
			return False;
		}
		if(fmt != COLOR_RGBA)
			value = (tmp | 0xff000000) & 0xffffffff;
		else
			value = ((tmp << 8) | 0x000000ff) & 0xffffffff;
		value = ConvertColorToAbgr(value, fmt);
		return True;
	}
}
// for remove useless spaces around seperators, begining and ending
static inline void _RemoveSpaceAroundSep(std::wstring& str)
{
	str = std::regex_replace(str, std::wregex(L"\\s*(,|:|^|$)\\s*"), L"$1");
}
// parset bool type configuration to T type value trueValue / falseValue
template <class T>
static void _RimeGetBool(RimeConfig* config, char* key, bool cond, T& value, const T& trueValue, const T& falseValue) {
	Bool tempb = False;
	if (RimeConfigGetBool(config, key, &tempb) || cond)
		value = (!!tempb) ? trueValue : falseValue;
}
//	parse string option to T type value, with fallback 
template <typename T>
void _RimeParseStringOptWithFallback(RimeConfig* config, const std::string key, T& value, const std::map<std::string, T> amap, const T& fallback) {
	char str_buff[256] = { 0 };
	if (RimeConfigGetString(config, key.c_str(), str_buff, sizeof(str_buff) - 1)) {
		auto it = amap.find(std::string(str_buff));
		value = (it != amap.end()) ? it->second : fallback;
	} 
	else value = fallback;
}
static inline void _abs(int* value) { *value = abs(*value); }	// turn *value to be non-negative
// get int type value with fallback key fb_key, and func to execute after reading
static void _RimeGetIntWithFallback(RimeConfig* config, const char* key, int* value, const char* fb_key = NULL, std::function<void(int*)> func = NULL)
{
	if (!RimeConfigGetInt(config, key, value) && fb_key != NULL) {
			RimeConfigGetInt(config, fb_key, value);
	}
	if (func) func(value);
}
// get string value, with fallback value *fallback, and func to execute after reading
static void _RimeGetStringWithFunc(RimeConfig* config, const char* key, std::wstring& value, const std::wstring* fallback = NULL, const std::function<void(std::wstring&)> func=NULL)
{
	const int BUF_SIZE = 2047;
	char buffer[BUF_SIZE + 1] = { 0 };
	if (RimeConfigGetString(config, key, buffer, BUF_SIZE)) {
		std::wstring tmp = string_to_wstring(buffer, CP_UTF8);
		if (func) func(tmp);
		value = tmp;
	}
	else if(fallback)
		value = *fallback;
}

void _UpdateShowNotificationsWhen(RimeConfig* config, UINT* show_notifications_when)
{
	char buffer[256] = { 0 };
	// if not set, shown always as default
	if (!RimeConfigGetString(config, "show_notifications_when", buffer, 256))
		*show_notifications_when = SHOWN_ALWAYS;
	else
	{
		std::string noti_str(buffer);
		if (std::regex_match(noti_str, std::regex(".*always.*")) || noti_str.empty())
			*show_notifications_when = SHOWN_ALWAYS;
		else if (noti_str == "never")
			*show_notifications_when = SHOWN_NEVER;
		else {
			*show_notifications_when = SHOWN_NEVER;
			if (std::regex_match(noti_str, std::regex(".*ascii_punct.*")))
				*show_notifications_when |= SHOWN_ASCII_PUNCT;
			if (std::regex_match(noti_str, std::regex(".*ascii_mode.*")))
				*show_notifications_when |= SHOWN_ASCII;
			if (std::regex_match(noti_str, std::regex(".*shape.*")))
				*show_notifications_when |= SHOWN_SHAPE;
			if (std::regex_match(noti_str, std::regex(".*schema.*")))
				*show_notifications_when |= SHOWN_SCHEMA;
			if (std::regex_match(noti_str, std::regex(".*simplification.*")))
				*show_notifications_when |= SHOWN_SIMPLIFICATION;
		}
	}
}
// update ui's style parameters, ui has been check before referenced 
static void _UpdateUIStyle(RimeConfig* config, UI* ui, bool initialize)
{
	UIStyle &style(ui->style());
	const int BUF_SIZE = 255;
	char buffer[BUF_SIZE + 1] = { 0 };
	// get font faces
	_RimeGetStringWithFunc(config, "style/font_face", style.font_face, NULL, _RemoveSpaceAroundSep);
	std::wstring* const pFallbackFontFace = initialize ? &style.font_face : NULL;
	_RimeGetStringWithFunc(config, "style/label_font_face", style.label_font_face, pFallbackFontFace, _RemoveSpaceAroundSep);
	_RimeGetStringWithFunc(config, "style/comment_font_face", style.comment_font_face, pFallbackFontFace, _RemoveSpaceAroundSep);
	// able to set label font/comment font empty, force fallback to font face.
	if (style.label_font_face.empty())
		style.label_font_face = style.font_face;
	if (style.comment_font_face.empty())
		style.comment_font_face = style.font_face;
	// get font points
	_RimeGetIntWithFallback(config, "style/font_point", &style.font_point);
	if (style.font_point <= 0)
		style.font_point = 12;
	_RimeGetIntWithFallback(config, "style/label_font_point", &style.label_font_point, "style/font_point", _abs);
	_RimeGetIntWithFallback(config, "style/comment_font_point", &style.comment_font_point, "style/font_point", _abs);
	_RimeGetIntWithFallback(config, "style/mouse_hover_ms", &style.mouse_hover_ms, NULL, _abs);
	_RimeGetIntWithFallback(config, "style/candidate_abbreviate_length", &style.candidate_abbreviate_length, NULL, _abs);
	_RimeGetBool(config, "style/inline_preedit", initialize, style.inline_preedit, true, false);
	_RimeGetBool(config, "style/vertical_auto_reverse", initialize, style.vertical_auto_reverse, true, false);
	const std::map<std::string, UIStyle::PreeditType> _preeditMap = { 
		{std::string("composition"),	UIStyle::COMPOSITION}, 
		{std::string("preview"),		UIStyle::PREVIEW}, 
		{std::string("preview_all"),	UIStyle::PREVIEW_ALL} 
	};
	_RimeParseStringOptWithFallback(config, "style/preedit_type", style.preedit_type, _preeditMap, style.preedit_type);
	const std::map < std::string , UIStyle::AntiAliasMode > _aliasModeMap = {
		{std::string("force_dword"),	UIStyle::FORCE_DWORD},
		{std::string("cleartype"),		UIStyle::CLEARTYPE},
		{std::string("grayscale"),		UIStyle::GRAYSCALE},
		{std::string("aliased"),		UIStyle::ALIASED},
		{std::string("default"),		UIStyle::DEFAULT}
	};
	_RimeParseStringOptWithFallback(config, "style/antialias_mode", style.antialias_mode, _aliasModeMap, style.antialias_mode);
	const std::map<std::string, UIStyle::LayoutAlignType> _alignType = {
		{std::string("top"),	UIStyle::ALIGN_TOP},
		{std::string("center"),	UIStyle::ALIGN_CENTER},
		{std::string("bottom"),	UIStyle::ALIGN_BOTTOM}
	};
	_RimeParseStringOptWithFallback(config, "style/layout/align_type", style.align_type, _alignType, style.align_type);
	_RimeGetBool(config, "style/display_tray_icon", initialize, style.display_tray_icon, true, false);
	_RimeGetBool(config, "style/ascii_tip_follow_cursor", initialize, style.ascii_tip_follow_cursor, true, false);
	_RimeGetBool(config, "style/horizontal", initialize, style.layout_type, UIStyle::LAYOUT_HORIZONTAL, UIStyle::LAYOUT_VERTICAL);
	_RimeGetBool(config, "style/paging_on_scroll", initialize, style.paging_on_scroll, true, false);
	_RimeGetBool(config, "style/click_to_capture", initialize, style.click_to_capture, true, false);
	_RimeGetBool(config, "style/fullscreen", false, style.layout_type, 
			((style.layout_type == UIStyle::LAYOUT_HORIZONTAL) ? UIStyle::LAYOUT_HORIZONTAL_FULLSCREEN : UIStyle::LAYOUT_VERTICAL_FULLSCREEN), style.layout_type);
	_RimeGetBool(config, "style/vertical_text", false, style.layout_type, UIStyle::LAYOUT_VERTICAL_TEXT, style.layout_type);
	_RimeGetBool(config, "style/vertical_text_left_to_right", false, style.vertical_text_left_to_right, true, false);
	_RimeGetBool(config, "style/vertical_text_with_wrap", false, style.vertical_text_with_wrap, true, false);
	const std::map<std::string, bool> _text_orientation = {
		{std::string("horizontal"), false},
		{std::string("vertical"), true}
	};
	bool _text_orientation_bool = false;
	_RimeParseStringOptWithFallback(config, "style/text_orientation", _text_orientation_bool, _text_orientation, _text_orientation_bool);
	if(_text_orientation_bool)
		style.layout_type = UIStyle::LAYOUT_VERTICAL_TEXT;
	_RimeGetStringWithFunc(config, "style/label_format", style.label_text_format);
	_RimeGetStringWithFunc(config, "style/mark_text", style.mark_text);
	_RimeGetIntWithFallback(config, "style/layout/min_width", &style.min_width, NULL, _abs);
	_RimeGetIntWithFallback(config, "style/layout/max_width", &style.max_width, NULL, _abs);
	_RimeGetIntWithFallback(config, "style/layout/min_height", &style.min_height, NULL, _abs);
	_RimeGetIntWithFallback(config, "style/layout/max_height", &style.max_height, NULL, _abs);
	// layout (alternative to style/horizontal)
	const std::map < std::string , UIStyle::LayoutType > _layoutMap = {
		{std::string("vertical"),					UIStyle::LAYOUT_VERTICAL},
		{std::string("horizontal"),					UIStyle::LAYOUT_HORIZONTAL},
		{std::string("vertical_text"),				UIStyle::LAYOUT_VERTICAL_TEXT},
		{std::string("vertical+fullscreen"),		UIStyle::LAYOUT_VERTICAL_FULLSCREEN},
		{std::string("horizontal+fullscreen"),		UIStyle::LAYOUT_HORIZONTAL_FULLSCREEN}
	};
	_RimeParseStringOptWithFallback(config, "style/layout/type", style.layout_type, _layoutMap, style.layout_type);
	// disable max_width when full screen
	if( style.layout_type == UIStyle::LAYOUT_HORIZONTAL_FULLSCREEN || style.layout_type == UIStyle::LAYOUT_VERTICAL_FULLSCREEN )
	{
		style.max_width = 0;
		style.inline_preedit = false;
	}
	_RimeGetIntWithFallback(config, "style/layout/border", &style.border, "style/layout/border_width", _abs);
	_RimeGetIntWithFallback(config, "style/layout/margin_x", &style.margin_x);
	_RimeGetIntWithFallback(config, "style/layout/margin_y", &style.margin_y);
	_RimeGetIntWithFallback(config, "style/layout/spacing", &style.spacing, NULL, _abs);
	_RimeGetIntWithFallback(config, "style/layout/candidate_spacing", &style.candidate_spacing, NULL, _abs);
	_RimeGetIntWithFallback(config, "style/layout/hilite_spacing", &style.hilite_spacing, NULL, _abs);
	_RimeGetIntWithFallback(config, "style/layout/hilite_padding_x", &style.hilite_padding_x, "style/layout/hilite_padding", _abs);
	_RimeGetIntWithFallback(config, "style/layout/hilite_padding_y", &style.hilite_padding_y, "style/layout/hilite_padding", _abs);
	_RimeGetIntWithFallback(config, "style/layout/shadow_radius", &style.shadow_radius, NULL, _abs);
	// disable shadow for fullscreen layout
	style.shadow_radius *= (!( style.layout_type == UIStyle::LAYOUT_HORIZONTAL_FULLSCREEN || style.layout_type == UIStyle::LAYOUT_VERTICAL_FULLSCREEN ));
	_RimeGetIntWithFallback(config, "style/layout/shadow_offset_x", &style.shadow_offset_x);
	_RimeGetIntWithFallback(config, "style/layout/shadow_offset_y", &style.shadow_offset_y);
	// round_corner as alias of hilited_corner_radius
	_RimeGetIntWithFallback(config, "style/layout/hilited_corner_radius", &style.round_corner, "style/layout/round_corner", _abs);
	// corner_radius not set, fallback to round_corner
	_RimeGetIntWithFallback(config, "style/layout/corner_radius", &style.round_corner_ex, "style/layout/round_corner", _abs);
	// fix padding and spacing settings
	if(style.layout_type != UIStyle::LAYOUT_VERTICAL_TEXT)
	{
		// hilite_padding vs spacing
		style.spacing = max(style.spacing, style.hilite_padding_y * 2);	// if hilite_padding over spacing, increase spacing
		// hilite_padding vs candidate_spacing
		if (style.layout_type == UIStyle::LAYOUT_VERTICAL_FULLSCREEN || style.layout_type == UIStyle::LAYOUT_VERTICAL) {
			style.candidate_spacing = max(style.candidate_spacing, style.hilite_padding_y * 2);	// vertical, if hilite_padding_y over candidate spacing, increase candidate spacing
		} else {
			style.candidate_spacing = max(style.candidate_spacing, style.hilite_padding_x * 2);	// horizontal, if hilite_padding_x over candidate spacing, increase candidate spacing
		}
		// hilite_padding_x vs hilite_spacing
		if(!style.inline_preedit)
			style.hilite_spacing = max(style.hilite_spacing, style.hilite_padding_x);
	}
	else	// LAYOUT_VERTICAL_TEXT
	{
		// hilite_padding_x vs spacing
		style.spacing = max(style.spacing, style.hilite_padding_x * 2); // if hilite_padding over spacing, increase spacing
		// hilite_padding vs candidate_spacing
		style.candidate_spacing = max(style.candidate_spacing, style.hilite_padding_x * 2); // if hilite_padding_x over candidate spacing, increase candidate spacing
		// vertical_text_with_wrap and hilite_padding_y over candidate_spacing
		if (style.vertical_text_with_wrap)
			style.candidate_spacing = max(style.candidate_spacing, style.hilite_padding_y * 2);
		// hilite_padding_y vs hilite_spacing
		if(!style.inline_preedit)
			style.hilite_spacing = max(style.hilite_spacing, style.hilite_padding_y);
	}
	// fix padding and margin settings
	int scale = style.margin_x < 0 ? -1 : 1;
	style.margin_x = scale * max(style.hilite_padding_x, abs(style.margin_x));
	scale = style.margin_y < 0 ? -1 : 1;
	style.margin_y = scale * max(style.hilite_padding_y, abs(style.margin_y));
	// get enhanced_position
	_RimeGetBool(config, "style/enhanced_position", initialize, style.enhanced_position, true, false);
	// get color scheme
	if (initialize && RimeConfigGetString(config, "style/color_scheme", buffer, BUF_SIZE))
		_UpdateUIStyleColor(config, style);
}
// load color configs to style, by "style/color_scheme" or specific scheme name "color" which is default empty
static bool _UpdateUIStyleColor(RimeConfig* config, UIStyle& style, std::string color)
{
	const int BUF_SIZE = 255;
	char buffer[BUF_SIZE + 1];
	memset(buffer, '\0', sizeof(buffer));
	std::string color_mark = "style/color_scheme";
	// color scheme
	if(RimeConfigGetString(config, color_mark.c_str(), buffer, BUF_SIZE) || !color.empty())
	{
		std::string prefix("preset_color_schemes/");
		prefix += (color.empty()) ? buffer : color;
		// define color format, default abgr if not set
		ColorFormat fmt = COLOR_ABGR;
		const std::map<std::string, ColorFormat> _colorFmt = {
			{std::string("argb"), COLOR_ARGB},
			{std::string("rgba"), COLOR_RGBA},
			{std::string("abgr"), COLOR_ABGR}
		};
		_RimeParseStringOptWithFallback(config, (prefix+"/color_format"), fmt, _colorFmt, COLOR_ABGR);
		_RimeConfigGetColor32bWithFallback(config, (prefix + "/back_color"), style.back_color, fmt, 0xffffffff);
		_RimeConfigGetColor32bWithFallback(config, (prefix + "/shadow_color"), style.shadow_color, fmt, TRANSPARENT_COLOR);
		_RimeConfigGetColor32bWithFallback(config, (prefix + "/prevpage_color"), style.prevpage_color, fmt, TRANSPARENT_COLOR);
		_RimeConfigGetColor32bWithFallback(config, (prefix + "/nextpage_color"), style.nextpage_color, fmt, TRANSPARENT_COLOR);
		_RimeConfigGetColor32bWithFallback(config, (prefix + "/text_color"), style.text_color, fmt, 0xff000000);
		_RimeConfigGetColor32bWithFallback(config, (prefix + "/candidate_text_color"), style.candidate_text_color, fmt, style.text_color);
		_RimeConfigGetColor32bWithFallback(config, (prefix + "/candidate_back_color"), style.candidate_back_color, fmt, TRANSPARENT_COLOR);
		_RimeConfigGetColor32bWithFallback(config, (prefix + "/border_color"), style.border_color, fmt, style.text_color);
		_RimeConfigGetColor32bWithFallback(config, (prefix + "/hilited_text_color"), style.hilited_text_color, fmt, style.text_color);
		_RimeConfigGetColor32bWithFallback(config, (prefix + "/hilited_back_color"), style.hilited_back_color, fmt, style.back_color);
		_RimeConfigGetColor32bWithFallback(config, (prefix + "/hilited_candidate_text_color"), style.hilited_candidate_text_color, fmt, style.hilited_text_color);
		_RimeConfigGetColor32bWithFallback(config, (prefix + "/hilited_candidate_back_color"), style.hilited_candidate_back_color, fmt, style.hilited_back_color);
		_RimeConfigGetColor32bWithFallback(config, (prefix + "/hilited_candidate_shadow_color"), style.hilited_candidate_shadow_color, fmt, TRANSPARENT_COLOR);
		_RimeConfigGetColor32bWithFallback(config, (prefix + "/hilited_shadow_color"), style.hilited_shadow_color, fmt, TRANSPARENT_COLOR);
		_RimeConfigGetColor32bWithFallback(config, (prefix + "/candidate_shadow_color"), style.candidate_shadow_color, fmt, TRANSPARENT_COLOR);
		_RimeConfigGetColor32bWithFallback(config, (prefix + "/candidate_border_color"), style.candidate_border_color, fmt, TRANSPARENT_COLOR);
		_RimeConfigGetColor32bWithFallback(config, (prefix + "/hilited_candidate_border_color"), style.hilited_candidate_border_color, fmt, TRANSPARENT_COLOR);
		_RimeConfigGetColor32bWithFallback(config, (prefix + "/label_color"), style.label_text_color, fmt, blend_colors(style.candidate_text_color, style.candidate_back_color));
		_RimeConfigGetColor32bWithFallback(config, (prefix + "/hilited_label_color"), style.hilited_label_text_color, fmt, blend_colors(style.hilited_candidate_text_color, style.hilited_candidate_back_color));
		_RimeConfigGetColor32bWithFallback(config, (prefix + "/comment_text_color"), style.comment_text_color, fmt, style.label_text_color);
		_RimeConfigGetColor32bWithFallback(config, (prefix + "/hilited_comment_text_color"), style.hilited_comment_text_color, fmt, style.hilited_label_text_color);
		_RimeConfigGetColor32bWithFallback(config, (prefix + "/hilited_mark_color"), style.hilited_mark_color, fmt, TRANSPARENT_COLOR);
		return true;
	}
	return false;
}

static void _LoadAppOptions(RimeConfig* config, AppOptionsByAppName& app_options)
{
	app_options.clear();
	RimeConfigIterator app_iter;
	RimeConfigIterator option_iter;
	RimeConfigBeginMap(&app_iter, config, "app_options");
	while (RimeConfigNext(&app_iter)) {
		AppOptions &options(app_options[app_iter.key]);
		RimeConfigBeginMap(&option_iter, config, app_iter.path);
		while (RimeConfigNext(&option_iter)) {
			Bool value = False;
			if (RimeConfigGetBool(config, option_iter.path, &value)) {
				options[option_iter.key] = !!value;
			}
		}
		RimeConfigEnd(&option_iter);
	}
	RimeConfigEnd(&app_iter);
}

void RimeWithWeaselHandler::_GetStatus(Status & stat, UINT session_id, Context& ctx)
{
	RIME_STRUCT(RimeStatus, status);
	if (RimeGetStatus(session_id, &status))
	{
		std::string schema_id = "";
		if(status.schema_id)
			schema_id = status.schema_id;
		stat.schema_name = string_to_wstring(status.schema_name, CP_UTF8);
		stat.schema_id = string_to_wstring(status.schema_id, CP_UTF8);
		stat.ascii_mode = !!status.is_ascii_mode;
		stat.composing = !!status.is_composing;
		stat.disabled = !!status.is_disabled;
		stat.full_shape = !!status.is_full_shape;
		if (schema_id != m_last_schema_id)
		{
			m_last_schema_id = schema_id;
			if(schema_id != ".default") {						// don't load for schema select menu
				RimeSetOption(session_id, "__synced", false); // Sync new schema options with front end
				bool inline_preedit = m_ui->style().inline_preedit;
				_LoadSchemaSpecificSettings(schema_id);
				_LoadAppInlinePreeditSet(session_id, true);
				if(m_ui->style().inline_preedit != inline_preedit)
					_UpdateInlinePreeditStatus(session_id);			// in case of inline_preedit set in schema
				_RefreshTrayIcon(session_id, _UpdateUICallback);	// refresh icon after schema changed
				if (m_show_notifications_when & SHOWN_SCHEMA) {
					ctx.aux.str = stat.schema_name;
					m_ui->Update(ctx, stat);
					m_ui->ShowWithTimeout(1200);
				}
			}
		}
		else
			_LoadAppInlinePreeditSet(session_id);
		RimeFreeStatus(&status);
	}
}

void RimeWithWeaselHandler::_GetContext(Context & weasel_context, UINT session_id)
{
	RIME_STRUCT(RimeContext, ctx);
	if (RimeGetContext(session_id, &ctx))
	{
		if (ctx.composition.length > 0)
		{
			weasel_context.preedit.str = string_to_wstring(ctx.composition.preedit, CP_UTF8);
			if (ctx.composition.sel_start < ctx.composition.sel_end)
			{
				TextAttribute attr;
				attr.type = HIGHLIGHTED;
				attr.range.start = utf8towcslen(ctx.composition.preedit, ctx.composition.sel_start);
				attr.range.end = utf8towcslen(ctx.composition.preedit, ctx.composition.sel_end);

				weasel_context.preedit.attributes.push_back(attr);
			}
		}
		if (ctx.menu.num_candidates)
		{
			CandidateInfo &cinfo(weasel_context.cinfo);
			_GetCandidateInfo(cinfo, ctx);
		}
		RimeFreeContext(&ctx);
	}
}

bool RimeWithWeaselHandler::_IsSessionTSF(UINT session_id)
{
	static char client_type[20] = { 0 };
	RimeGetProperty(session_id, "client_type", client_type, sizeof(client_type) - 1);
	return std::string(client_type) == "tsf";
}

void RimeWithWeaselHandler::_UpdateInlinePreeditStatus(UINT session_id)
{
	if (!m_ui)	return;
	// set inline_preedit option
	bool inline_preedit = m_ui->style().inline_preedit && _IsSessionTSF(session_id);
	RimeSetOption(session_id, "inline_preedit", Bool(inline_preedit));
	// show soft cursor on weasel panel but not inline
	RimeSetOption(session_id, "soft_cursor", Bool(!inline_preedit));
}

